/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.scoring;

import java.util.Comparator;
import java.util.EnumSet;
import java.util.logging.Logger;

import edu.ucsd.db.bassarisk.exception.QueryValidationException;
import edu.ucsd.db.bassarisk.query.Constants;
import edu.ucsd.db.bassarisk.query.ValidationTraversalLog;
import edu.ucsd.db.bassarisk.query.path.DSDBuilder;
import edu.ucsd.db.bassarisk.query.path.DSDEdgeBuilder;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDContext;
import edu.ucsd.db.bassarisk.query.path.DSDElements.DSDElement;
import edu.ucsd.db.bassarisk.query.path.DSDFilterBuilder;
import edu.ucsd.db.bassarisk.query.path.DSDSchemaInitializeVisitor;
import edu.ucsd.db.bassarisk.query.path.DataSequenceDefinition;
import edu.ucsd.db.bassarisk.schema.DataSequenceClass;
import edu.ucsd.db.bassarisk.schema.Schema;
import edu.ucsd.db.bassarisk.schema.Schema.ValueType;

public class ScoringSignalDefinition extends DataSequenceDefinition {
  public static enum AcceptingParameters {
    /**
     * Flag indicates scoring signal depends on a particular token, and more
     * specifically, it considers all instances of that token within the answer
     * run (whether or not they satisfy other predicates).
     * <p>
     * <h5>Example:</h5> Consider the "document-frequency" signal. This signal
     * is usually defined with respect to the context node and the all-tokens
     * parameter: <code>TF := .//#count:_POSTION_[@word=_ALL_TOKENS_]</code>
     */
    AllTokens,
    /**
     * Flag indicates scoring signal takes the current context node (query
     * answer) as a parameter. This is useful for defining per-document signals
     * like term-frequency. *
     * <p>
     * <h5>Example:</h5> Consider the "document-length" signal. This query is
     * defined with respect to the context node. The context node is the leading
     * dot in the path notation: <code>DL := ./@length</code>
     */
    ContextNode,
    /**
     * Flag indicates scoring signal takes the path from root to the context
     * nodes (query answers) as a parameter. This implies a signal computed
     * against all potential answers (without any filter on them).
     * <p>
     * <h5>Example:</h5> Consider the "average document length" signal. The
     * "document" depends on the contexts path. If the "document" is article
     * abstracts, then the ContextPath is
     * <code>_PATH_ := /index/article/abstract</code> vs "documents" that are
     * article sections where <code>_PATH_ := /index/article/body/section</code>
     * . Since the "document" is query dependent, we define
     * "average document length" against the context path <code>_PATH_</code>.
     * <code>ADL := /_PATH_/#avg:@length</code>
     * 
     */
    ContextsPath,
    /**
     * Flag indicates scoring signal depends on a particular token, and more
     * specifically, it considers only those instances of the keyword that
     * satisfy all other predicates in the query.
     * <p>
     * <h5>Example:</h5> Consider the "document-frequency" signal. A variation
     * on this signal excludes from the count those token-positions that do not
     * appear in any match:
     * <code>TF := .//#count:_POSTION_[@word=_FILTERED_TOKENS_]</code>
     */
    FilteredTokens
  }

  public static class ScoringInputDefinitionBuilder extends
      DSDBuilder<ScoringInputDefinitionBuilder> {
    private final EnumSet<AcceptingParameters> scoringSignalParamSet = EnumSet
        .noneOf(AcceptingParameters.class);

    private ScoringInputDefinitionBuilder(final ValueType type) {
      super(type);
    }

    private ScoringInputDefinitionBuilder addContext() {
      this.appendElement(new DSDContext());
      this.scoringSignalParamSet.add(AcceptingParameters.ContextNode);
      return this;
    }

    @Override
    public DSDEdgeBuilder<ScoringInputDefinitionBuilder>
        filter(final String attributeName, final String filterValue) {
      if (filterValue.equals(Constants.AllTokens)) {
        this.scoringSignalParamSet.add(AcceptingParameters.AllTokens);
      } else if (filterValue.equals(Constants.FilteredTokens)) {
        this.scoringSignalParamSet.add(AcceptingParameters.FilteredTokens);
      }
      return super.filter(attributeName, filterValue);
    }

    @Override
    public DSDFilterBuilder<ScoringInputDefinitionBuilder> run(final String name) {
      if (name.equals(Constants.ContextsPath)) {
        if (this.scoringSignalParamSet.contains(AcceptingParameters.ContextsPath)) {
          String msg =
              "Invalid use. ContextsPath may be used once per singal definition.";
          throw new RuntimeException(msg);
        }
        this.scoringSignalParamSet.add(AcceptingParameters.ContextsPath);
      }
      return super.run(name);
    }

    public ScoringSignalDefinition toDefinition() {
      return new ScoringSignalDefinition(this.getHead(), this.scoringSignalParamSet,
                                         this.type);
    }
  }

  private static final Logger LOG = Logger.getLogger(ScoringSignalDefinition.class
      .getName());

  public static DSDEdgeBuilder<ScoringInputDefinitionBuilder>
      fromContext(final ValueType type) {
    return new ScoringInputDefinitionBuilder(type).addContext();
  }

  public static DSDEdgeBuilder<ScoringInputDefinitionBuilder>
      fromRoot(final ValueType type) {
    return new ScoringInputDefinitionBuilder(type);
  }

  private final EnumSet<AcceptingParameters> params;

  private ScoringSignalDefinition(final DSDElement head,
                                  final EnumSet<AcceptingParameters> scoringSignalParamSet,
                                  final ValueType type) {
    super(head, type);
    this.params = scoringSignalParamSet;
  }

  public void expandContextsPath(final Comparator<DataSequenceClass> runClassComparator,
                                 final DataSequenceClass answerContextClass,
                                 final DataSequenceClass queryRootClass) {
    ContextsPathExpansionVisitor visitor =
        new ContextsPathExpansionVisitor(runClassComparator, answerContextClass,
                                         queryRootClass);
    this.applyTransformation(visitor, null);
  }

  public boolean hasParameter(final AcceptingParameters param) {
    return this.params.contains(param);
  }

  public void initailize(final Schema schema, final DataSequenceClass rootRunClass)
      throws QueryValidationException {
    ValidationTraversalLog vtl = new ValidationTraversalLog();
    DSDSchemaInitializeVisitor visitor = new DSDSchemaInitializeVisitor(schema, vtl);
    this.applyVisitor(visitor, rootRunClass);
    vtl.checkLog(LOG);
  }
}
